S03-03 JS-基础-对象
[TOC]
面向对象
对象
语法特性
对象(Object):是一种复合数据类型,用于存储键值对(key-value pairs)的集合。
为什么需要对象类型:
基本数据类型可以存储一些简单的值,但是现实世界的事物抽象成程序时,往往比较复杂:
- 比如一个人,有自己的特性(比如姓名、年龄、身高),有一些行为(比如跑步、学习、工作)。
- 比如一辆车,有自己的特性(比如颜色、重量、速度),有一些行为(比如行驶)。
这个时候,我们需要一种新的类型将这些特性和行为组织在一起,这种类型就是对象类型。
核心特性:
键值对结构:
对象由
属性(property)
组成,每个属性包含:- 键(Key): 字符串或 Symbol 类型(唯一标识符)。
- 值(Value): 任意数据类型(字符串、数字、函数、数组,甚至其他对象)。
属性之间是以逗号( comma )分割
jslet user = { name: "Alice", // 键: "name", 值: "Alice" age: 30, // 键: "age", 值: 30 isAdmin: true, // 键: "isAdmin", 值: true sayHello: function() { console.log("Hello!") } // 键: "sayHello", 值: 函数 };
动态性:可随时添加/删除属性
jsuser.email = "alice@example.com"; // 添加新属性 delete user.isAdmin; // 删除属性
引用类型:
对象是引用类型。赋值时传递的是内存地址(而非值副本):
jslet obj1 = { a: 1 }; let obj2 = obj1; // obj2 和 obj1 指向同一对象,传递的是内存地址 obj2.a = 2; // 修改 obj2 会影响 obj1 console.log(obj1.a); // 输出 2
方法(Method):
当值为函数时,该属性称为对象的方法:
jsuser.sayHello(); // 调用对象方法 → 输出 "Hello!"
原型链(Prototype):
对象通过原型链实现继承。每个对象都有一个隐藏属性
[[Prototype]]
(可通过__proto__
或Object.getPrototypeOf()
访问)。
创建对象
创建对象的常用方式:
对象的创建方法有很多,包括三种:
- 对象字面量:最常用
- new Object():
- new 构造函数:用于创建多个相似对象
Object.create()
:可以指定原型
对象字面量:
最常用,直接使用 {}
语法创建对象,适合创建单个对象。
const person = {
name: "张三",
age: 30,
greet() {
return `你好,我是${this.name}`;
}
};
console.log(person.greet()); // "你好,我是张三"
new Object():
使用内置的 Object
构造函数创建
const car = new Object();
car.brand = "Toyota";
car.model = "Camry";
car.drive = function() {
return `驾驶${this.brand} ${this.model}`;
};
new 构造函数:
使用 new
关键字和自定义构造函数
function Product(name, price) {
this.name = name;
this.price = price;
this.getInfo = function() {
return `${this.name}: ¥${this.price}`;
};
}
const product1 = new Product("手机", 2999);
const product2 = new Product("耳机", 599);
Object.create()
:
基于现有对象创建新对象,可以指定原型
const personProto = {
greet() {
return `你好,我是${this.name}`;
}
};
const john = Object.create(personProto);
john.name = "John";
john.age = 28;
// 创建带属性的对象
const mary = Object.create(personProto, {
name: { value: "Mary" },
age: { value: 32 }
});
操作对象
属性访问
点表示法:
.
,用于已知且有效的变量标识符jsconst person = { name: "Alice" }; console.log(person.name); // "Alice"
方括号表示法:
[]
,用于动态属性名或包含特殊字符的属性jsconsole.log(person["name"]); // "Alice"
键名格式:键名必须放在引号里面,否则会被当作变量处理。
jsconst key = "age"; console.log(person["name"]); // "Alice" console.log(person[key]); // 30 (使用变量) // 特殊属性名 person["home address"] = "Beijing";
使用表达式:方括号运算符内部还可以使用表达式。
jsobj['hello' + ' world'] obj[3 + 3]
属性添加/修改
属性添加:JS 允许属性的“后绑定”,也就是说,你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性。
var obj = {};
// 添加新属性
obj.foo = 'Hello'; // 点运算符
obj['bar'] = 'World'; // 方括号运算符
属性修改:语法和属性添加类似,添加一个已存在的属性,就会修改该属性的值。
const car = { brand: "Toyota" };
// 修改现有属性
car.brand = "Honda";
属性删除 delete
delete
命令:用于删除对象的属性:删除成功后返回true
。
var obj = { p: 1 };
delete obj.p // true
obj.p // undefined
删除一个不存在的属性:delete
不报错,而且返回true
。
var obj = {};
delete obj.p // true
只有一种情况,delete
命令会返回false
:那就是该属性存在,且不得删除。
var obj = Object.defineProperty({}, 'p', {
value: 123,
configurable: false
});
obj.p // 123
delete obj.p // false
delete
命令只能删除对象本身的属性,无法删除继承的属性
var obj = {};
delete obj.toString // true
obj.toString // function toString() { [native code] }
属性枚举
查看一个对象本身的所有属性,可以使用Object.keys
方法。
Object.keys():(obj)
,用于获取对象的所有可枚举属性名。
var obj = {
key1: 1,
key2: 2
};
Object.keys(obj); // ['key1', 'key2']
属性遍历 for...in
for...in
循环:用来遍历一个对象的全部属性。
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
console.log('键名:', i);
console.log('键值:', obj[i]);
}
// 键名: a 键值: 1
// 键名: b 键值: 2
// 键名: c 键值: 3
for...in
循环有两个使用注意点:
只遍历可遍历属性:它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
jsvar obj = {}; // toString 属性是存在的 obj.toString // toString() { [native code] } for (var p in obj) { console.log(p); } // 没有任何输出
它不仅遍历对象自身的属性,还遍历继承的属性:一般需求都是只遍历自身的属性,所以需要使用
hasOwnProperty()
方法过滤掉继承的属性jsvar person = { name: '老张' }; for (var key in person) { if (person.hasOwnProperty(key)) { console.log(key); // name } }
for
循环:还可以使用 for
循环来遍历对象的属性。
属性是否存在 in
in
运算符:用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true
,否则返回false
。
var obj = { p: 1 };
'p' in obj // true
识别继承属性:
in
运算符不能识别哪些属性是对象自身的,哪些属性是继承的。jsvar obj = { p: 1 }; 'p' in obj // true 'toString' in obj // true
可以使用对象的
hasOwnProperty()
方法判断一下,是否为对象自身的属性。jsvar obj = {}; if ('toString' in obj) { console.log(obj.hasOwnProperty('toString')) // false }
栈内存/堆内存
我们知道程序是需要加载到内存中来执行的,我们可以将内存划分为两个区域:栈内存和堆内存。
- 原始类型:占据的空间是在栈内存中分配的;
- 对象类型:占据的空间是在堆内存中分配的;
后续我们会学习图中的其他知识
目前我们先掌握堆和栈的概念即可
值类型/引用类型【
值类型
值类型(Primitive Types,原始类型):是不可变的数据类型,它们直接存储在变量访问的位置(栈),当你操作它们时,你操作的是实际的原始值。
按值传递:传递的是值的副本。
let a = 10;
let b = a; // b 获取的是 a 的值的副本
a = 20;
console.log(a); // 20
console.log(b); // 10(不受 a 变化影响)
存储/比较的都是原始值本身:而不是指向内存中位置的引用
console.log(5 === 5); // true(值相同)
console.log('hi' === 'hi'); // true
引用类型
引用类型(对象类型):是指那些值存储在堆内存中,而变量保存的是内存地址(引用) 的数据类型。当你操作引用类型时,实际上是在操作指向实际数据存储位置的指针,而不是直接操作数据本身。
// 引用类型(对象类型)
let b = { value: 20 }; // 变量 b 存储的是内存地址,指向实际对象
变量保存的是内存地址(引用):实际值存储在堆内存中,在变量中保存的是对象的“引用”。
let obj1 = { id: 1 };
let obj2 = obj1; // obj2 获得的是 obj1 的引用(内存地址)
obj2.id = 2; // 修改 obj2 会影响 obj1
console.log(obj1.id); // 输出 2
值类型 vs 引用类型
比较两个值/对象:
// 比较两个值
let m = 123
let n = 123
console.log(m === n) // true
// 比较两个对象
let a = {}
let b = {}
console.log(a === b) // false
函数参数传递:
值类型参数:传递值的副本
jsfunction changeValue(num) { num = 100; } let original = 50; changeValue(original); console.log(original); // 50 (未改变)
引用类型参数:传递引用的副本(仍指向同一对象)
jsfunction updateProfile(user) { user.age = 30; } let person = { name: "Alice" }; updateProfile(person); console.log(person); // {name: "Alice", age: 30} (已修改)
this
为什么需要this
其他语言中的this:
在常见的编程语言中,几乎都有this这个关键字(Objective-C中使用的是self),但是JavaScript中的this和常见的面向对象语言中的this不太一样:
常见面向对象的编程语言中,比如Java、C++、Swift、Dart等等一系列语言中,this通常只会出现在类的方法中。
也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象;
JS中的this:
但是JS中的this更加灵活,无论是它出现的位置还是它代表的含义;
有this和没有this的区别:
我们来看一下编写一个obj的对象,有this和没有this的区别:
this指向什么
this
总是指向一个对象:
// 在全局中,this指向window对象
this // window
// 在函数中,this指向调用它的对象
function fn() {
console.log(this)
}
var obj = {}
obj.fn = fn
obj.fn() // obj
this
就是属性或方法“当前”所在的对象:
var person = {
name: '张三',
describe: function () {
return '姓名:'+ this.name; // this指向当前的person对象
}
};
person.describe()// "姓名:张三"
this
的指向是可变的:
由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,这会导致this也是可变的。
var A = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
};
var B = {
name: '李四'
};
B.describe = A.describe;
B.describe() // "姓名:李四"
this的本质【
函数在内存中的保存方式:
类和对象的思维方式
我们来思考一个问题:如果需要在开发中创建一系列的相似的对象,我们应该如何操作呢?
比如下面的例子:
- 游戏中创建一系列的英雄(英雄具备的特性是相似的,比如都有名字、技能、价格,但是具体的值又不相同)
- 学生系统中创建一系列的学生(学生都有学号、姓名、年龄等,但是具体的值又不相同)
当然,一种办法是我们创建一系列的对象:
这种方式有一个很大的弊端:创建同样的对象时,需要编写重复的代码;
- 我们是否有可以批量创建对象,但是又让它们的属性不一样呢?
创建对象的方案 – 工厂函数
我们可以想到的一种创建对象的方式:工厂函数
- 我们可以封装一个函数,这个函数用于帮助我们创建一个对象,我们只需要重复调用这个函数即可;
- 工厂模式其实是一种常见的设计模式;
认识构造函数
工厂方法创建对象有一个比较大的问题:我们在打印对象时,对象的类型都是Object类型
- 但是从某些角度来说,这些对象应该有一个他们共同的类型;
- 下面我们来看一下另外一种模式:构造函数的方式;
我们先理解什么是构造函数?
- 构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数;
- 在其他面向的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法;
- 但是JavaScript中的构造函数有点不太一样,构造函数扮演了其他语言中类的角色;
也就是在JavaScript中,构造函数其实就是类的扮演者:
- 比如系统默认给我们提供的Date就是一个构造函数,也可以看成是一个类;
- 在ES5之前,我们都是通过function来声明一个构造函数(类)的,之后通过new关键字来对其进行调用;
- 在ES6之后,JavaScript可以像别的语言一样,通过class来声明一个类;
那么类和对象到底是什么关系呢?
类和对象的关系
那么什么是类(构造函数)呢?
- 现实生活中往往是根据一份描述/一个模板来创建一个实体对象的.
- 编程语言也是一样, 也必须先有一份描述, 在这份描述中说明将来创建出来的对象有哪些属性(成员变量)和行为(成员方法)
比如现实生活中,我们会如此来描述一些事物:
- 比如水果fruits是一类事物的统称,苹果、橘子、葡萄等是具体的对象;
- 比如人person是一类事物的统称,而Jim、Lucy、Lily、李雷、韩梅梅是具体的对象;
比如植物大战僵尸游戏
JavaScript中的类(ES5)
我们前面说过,在JavaScript中类的表示形式就是构造函数。
JavaScript中的构造函数是怎么样的?
- 构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别;
- 那么如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数;
如果一个函数被使用new操作符调用了,那么它会执行如下操作:
- 在内存中创建一个新的对象(空对象);
- 这个对象内部的 [[prototype]] 属性会被赋值为该构造函数的prototype属性;(后面详细讲);
- 构造函数内部的this,会指向创建出来的新对象;
- 执行函数的内部代码(函数体代码);
- 如果构造函数没有返回非空对象,则返回创建出来的新对象;
接下来,我们可以用构造函数的方式来实现一下批量创建学生。
创建对象的方案 – 构造函数(类)
我们来通过构造函数实现一下:
这个构造函数可以确保我们的对象是有Person的类型的(实际是constructor的属性,这个我们后续再探讨);
事实上构造函数还有很多其他的特性:
- 比如原型、原型链、实现继承的方案
- 比如ES6中类、继承的实现;
在 JavaScript 中,对象(Object) 是一种复合数据类型,用于存储键值对(key-value pairs)的集合。它是 JavaScript 最核心的概念之一,几乎所有元素都可以被视为对象(如函数、数组、日期等)。
核心特性:
键值对结构 对象由
属性(property)
组成,每个属性包含:- 键(Key): 字符串或 Symbol 类型(唯一标识符)。
- 值(Value): 任意数据类型(字符串、数字、函数、数组,甚至其他对象)。
jslet user = { name: "Alice", // 键: "name", 值: "Alice" age: 30, // 键: "age", 值: 30 isAdmin: true, // 键: "isAdmin", 值: true sayHello: function() { console.log("Hello!") } // 键: "sayHello", 值: 函数 };
添加/删除属性
jsuser.email = "alice@example.com"; // 添加新属性 delete user.isAdmin; // 删除属性
引用类型
对象是引用类型。赋值时传递的是内存地址(而非值副本):
jslet obj1 = { a: 1 }; let obj2 = obj1; // obj2 和 obj1 指向同一对象 obj2.a = 2; // 修改 obj2 会影响 obj1 console.log(obj1.a); // 输出 2
方法(Method)
当值为函数时,该属性称为对象的方法:
jsuser.sayHello(); // 调用对象方法 → 输出 "Hello!"
原型链(Prototype)
对象通过原型链实现继承。每个对象都有一个隐藏属性
[[Prototype]]
(可通过__proto__
或Object.getPrototypeOf()
访问)。
创建对象的常用方式:
字面量语法(最常用)
jslet person = { name: "Bob", greet() { console.log("Hi!") } };
new Object()
构造函数jslet car = new Object(); car.brand = "Toyota";
构造函数(用于创建多个相似对象)
jsfunction Person(name) { this.name = name; } let alice = new Person("Alice");
Object.create()
(指定原型)jslet prototype = { log: () => console.log("Prototype") }; let obj = Object.create(prototype); // obj 继承 prototype
操作对象:
操作 | 示例 |
---|---|
访问属性 | user.name 或 user["name"] |
添加属性 | user.city = "Paris" |
删除属性 | delete user.age |
检查属性存在 | "name" in user 或 user.hasOwnProperty("name") |
遍历属性 | for (let key in user) { console.log(key) } |
内置对象方法:
Object.keys(obj)
:返回所有可枚举属性的键数组。Object.values(obj)
:返回属性值数组。Object.entries(obj)
:返回键值对数组(如[ ["name", "Alice"], ["age", 30] ]
)。Object.assign(target, src)
:合并对象(浅拷贝)。
特殊对象类型:
类型 | 说明 |
---|---|
数组 | 有序集合,键为数字索引。let arr = [1, 2, 3] |
函数 | 可执行对象,含 call() /apply() 等方法。 |
内置对象 | Date , Math , JSON , RegExp 等。 |